﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.OpenGL;
using Silvermoon.Controls;
using System.Drawing;
using System.Threading;
using Silvermoon.Controls.Sys;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Shapes;
using Microsoft.WindowsCE.Forms;
using Silvermoon.Controls.Base;
using Silvermoon.Windows;
using System.Diagnostics;

namespace Silvermoon.Core
{
    /// <summary>
    /// Manages presentation and message exchange of controls.
    /// </summary>
    public sealed class Screen : IDisposable
    {
        #region constants

        bool showhardwareButtons = !true;

        const int hardwarePanelSize = 14;

        /// <summary>
        /// Silvermoon is based on a DPI of 192 which is th standard for displays for 480x800, 480x640 or similiar:
        /// </summary>
        private const float ScaleDPI = 192f;

        #endregion
        #region Fields

        private int threadId;
        private HardwareKeyPanel hardwareKeys;
        private InputPanel inputPanel;
        private AnimatedControl cursor;
        private AnimatedControl contextMenuDelayControl;
        private bool showBusyCursor;
        private TimeEventServer eventTimer;
        private MessageServer messageServer;
        private Size clientSize;
        private ControlCollection screenControls;
        private System.Windows.Forms.Form window;

        #endregion
        #region ctor/dtor

        static Screen()
        {
            ControlScaleFactor = new SizeF(1f, 1f);
            IsScaled = ControlScaleFactor.Width != 1F || ControlScaleFactor.Height != 1F;
            // if (IsScaled) FontSprite.DefaultFontHeight = 9F * ControlScaleFactor.Width;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="screen"/> class.
        /// </summary>
        /// <param name="window">The window.</param>
        public Screen(System.Windows.Forms.Form window)
        {
            threadId = Thread.CurrentThread.ManagedThreadId;
            if (window == null) throw new ArgumentNullException("window");
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnGlobalException);

            this.window = window;
            Screen.Current = this;
            Host = window;
            window.Disposed += OnWindowDisposed;
            window.Resize += OnResize;
            window.Closed +=  OnWindowClosed;
            window.Deactivate += OnDeactivate;
            window.Activated += OnActivated;

            clientSize = window.ClientSize;

            Device = new Device(window);
            Renderer = new Renderer(Device, window.ClientRectangle);
            screenControls = new ControlCollection(null);

            // A form or control does not get the correct key code data for input, therefore a dummy TextBox is generated that gets
            // the input focus:
            System.Windows.Forms.TextBox tb = new System.Windows.Forms.TextBox();
            tb.Size = new Size(0, 0);
            window.Controls.Add(tb);
            tb.Focus();
            inputPanel = new InputPanel();
            inputPanel.Enabled = false;

            messageServer = new MessageServer(window, tb);
            messageServer.Controls = screenControls;

            eventTimer = new TimeEventServer(this);
            eventTimer.Tick += OnLoadedTick;
            eventTimer.Enabled = true;
            if (showhardwareButtons && hardwareKeys == null) CreateHardwarePanel();
            Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
        }




        /// <summary>
        /// Disposes the instance and releases all resources.
        /// You should call this method if you no longer require <see cref="screen"/>.
        /// </summary>
        public void Dispose()
        {
            window.Closed -= OnWindowClosed;
            window.Deactivate -= OnDeactivate;
            window.Activated -= OnActivated;

            if (eventTimer != null)
            {
                eventTimer.Enabled = false;
                eventTimer.Dispose();
                eventTimer = null;
            }
            DisposeControls();

            if (Device != null) Device.Dispose();
            Device = null;

            if (cursor != null) cursor.Dispose();
            cursor = null;
            if (contextMenuDelayControl != null) contextMenuDelayControl.Dispose();
            contextMenuDelayControl = null;

            Current = null;
            if (messageServer != null) messageServer.Dispose();
            messageServer = null;
            Renderer = null;
            AppDomain.CurrentDomain.UnhandledException -= OnGlobalException;
        }

        private void DisposeControls()
        {
            if (screenControls != null) screenControls.ClearAndDispose();
        }

        #endregion
        #region Properties

        public ControlCollection Controls { get { return screenControls; } }

        public Window ActiveWindow { get; private set; }


        /// <summary>
        /// Gets the orientation of the screen.
        /// </summary>
        public Orientation Orientation
        {
            get { return clientSize.Width > clientSize.Height ? Orientation.Landscape : Orientation.Portrait; }
        }

        public bool IsBusy
        {
            get { return showBusyCursor; }
            set
            {
                if (showBusyCursor != value) ChangeBusyCursor(value);
            }
        }


        public float TicksPerFrame { get { return eventTimer != null ? eventTimer.TicksPerFrame : 1; } }

        /// <summary>
        /// Gets the width of the render target in pixel.
        /// </summary>
        public int Width { get { return clientSize.Width; } }

        /// <summary>
        /// Gets the height of the render target in pixel.
        /// </summary>
        public int Height { get { return clientSize.Height; } }

        /// <summary>
        /// Gets the size of the rende target in pixel.
        /// </summary>
        public Size Size { get { return clientSize; } }

        /// <summary>
        /// Gets the instance. Note that there can be only one screen at one time.
        /// </summary>
        public static Screen Current { get; private set; }

        /// <summary>
        /// Gets the open gl device for rendering.
        /// </summary>
        public Device Device { get; private set; }

        /// <summary>
        /// Gets the <see cref="Renderer"/> class which renders controls.
        /// </summary>
        public Renderer Renderer { get; private set; }

        /// <summary>
        /// Gets the <see cref="T:System.Windows.Forms.Control"/> in which the controls are rendered.
        /// </summary>
        public System.Windows.Forms.Form Host { get; private set; }

        /// <summary>
        /// Gets the scale factor for controls that descibe how to scale the underlying rendering against the specified bounds,
        /// </summary>
        public static SizeF ControlScaleFactor { get; private set; }

        /// <summary>
        /// Gets whether the dimensions of controls are scaled against their underlying graphical shapes and rendering.
        /// </summary>
        public static bool IsScaled { get; private set; }

        /// <summary>
        /// Gets the control that currently has the focus.
        /// </summary>
        public Control FocusedControl { get { return messageServer.FocusedControl; } }

        public MessageServer MessageServer { get { return messageServer; } }


        #endregion
        #region Methods

        private void OnActivated(object sender, EventArgs e)
        {
            if (eventTimer != null)
            {
                eventTimer.Enabled = true;
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
            }

            //   Device.Resume();

        }

        private void OnDeactivate(object sender, EventArgs e)
        {
            if (eventTimer != null)
            {
                eventTimer.Enabled = false;
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
            }
            //if (Device != null) Device.Suspend();
        }

        /// <summary>
        /// Occurs on any unhandled exception.
        /// </summary>
        private void OnGlobalException(object sender, UnhandledExceptionEventArgs e)
        {
            Dispose();
        }

        private void OnResize(object sender, EventArgs e)
        {
            if (clientSize != Host.ClientSize)
            {
                bool perspective = Renderer.Perspective;
                Color color = Renderer.BackgroundColor;
                Renderer.Change(Host.ClientRectangle);
                Renderer.Perspective = perspective;
                Renderer.BackgroundColor = color;

                clientSize = Host.ClientSize;
                OnOrientationChanged();

                Control c = screenControls.First;
                while (c != null)
                {
                    Layouter.Layout(c, clientSize);
                    c = c.NextControl;
                }
                LayoutHardwareKeys();
            }
        }

        private void LayoutHardwareKeys()
        {
            if (hardwareKeys != null)
            {
                bool landscape = Width > Height;
                hardwareKeys.Landscape = landscape;
                Size size = this.Size;
                if (!landscape)
                {
                    size.Height -= hardwarePanelSize;
                    hardwareKeys.Bounds = new Rectangle(0, size.Height, size.Width, hardwarePanelSize);
                }
                else
                {
                    size.Width -= hardwarePanelSize;
                    hardwareKeys.Bounds = new Rectangle(size.Width, 0, hardwarePanelSize, size.Height);
                }
            }
        }

        private void OnWindowClosed(object sender, EventArgs e)
        {
             Dispose();
        }

        private void OnLoadedTick(object sender, TimeEventArgs e)
        {
            eventTimer.Tick -= OnLoadedTick;
            OnLoaded();
        }

        /// <summary>
        /// Occurs when the window that created the screen is disposed.
        /// </summary>
        private void OnWindowDisposed(object sender, EventArgs e)
        {
            Current = null;
            Dispose();
        }

        private void ChangeBusyCursor(bool value)
        {
            showBusyCursor = value;
            if (!value)
            {
                if (this.cursor != null)
                {
                    cursor.Enabled = false;
                    this.Detach(cursor);
                    Invalidate();
                }
            }
            else
            {
                if (cursor == null) cursor = new AnimatedControl();
                cursor.Location = new Point((Width - cursor.Width) / 2, (Height - cursor.Height) / 2);
                cursor.Visible = true;
                cursor.Enabled = true;
                Attach(cursor);
                Invalidate();
            }
        }

        internal void SetContextMenuDelay(bool visible, Point position)
        {
            if (!visible)
            {
                if (contextMenuDelayControl != null)
                {
                    contextMenuDelayControl.Enabled = false;
                    this.Detach(contextMenuDelayControl);
                }
            }
            else
            {
                if (contextMenuDelayControl == null)
                {
                    contextMenuDelayControl = new AnimatedControl { StartDelay = 300 };
                    contextMenuDelayControl.Size = Control.ScaledSize(160, 160);
                    contextMenuDelayControl.Opacity = 180;
                    contextMenuDelayControl.Invalidate(InvalidationFlag.All);
                }
                contextMenuDelayControl.Location = new Point(position.X - contextMenuDelayControl.Width / 2, position.Y - contextMenuDelayControl.Height / 2);
                contextMenuDelayControl.Visible = true;
                contextMenuDelayControl.Enabled = true;
                Attach(contextMenuDelayControl);
            }
        }

        /// <summary>
        /// Invalidates a control. If a control is Invalid, it must be rendered again.
        /// </summary>
        public void Invalidate()
        {
            Renderer.Invalidated = true;
        }

        /// <summary>
        /// Renders all controls.
        /// </summary>
        public void Update()
        {
            if (Renderer != null && !Device.IsSuspended && Renderer.Invalidated)
            {
                Renderer.Render(screenControls);
            }
        }

        /// <summary>
        /// Progresses animations and updates if necassary.
        /// This method can be used within long processing methods to update the current state while obtaining the thread completely.
        /// </summary>
        public void Progress()
        {
            eventTimer.Progress();
        }

        private void CreateHardwarePanel()
        {
            hardwareKeys = new HardwareKeyPanel();
            screenControls.AddFirst(hardwareKeys);
            LayoutHardwareKeys();
        }

        public bool IsRendering { get { return Renderer != null ? Renderer.IsRendering : false; } }

        /// <summary>
        /// Creates the only instance.
        /// </summary>
        /// <param name="window">The form in which the controls are presented.</param>
        /// <returns>The  <see cref="screen"/> instance.</returns>
        public static Screen Initialize(System.Windows.Forms.Form window)
        {
            // the base dpi for Silvermoon are 192 dpi, other displays must be scaled:
            float scaleX = ((float)window.CurrentAutoScaleDimensions.Width) / ScaleDPI;
            float scaleY = ((float)window.CurrentAutoScaleDimensions.Height) / ScaleDPI;
            Screen.ControlScaleFactor = new SizeF(scaleX, scaleY);
            IsScaled = ControlScaleFactor.Width != 1F || ControlScaleFactor.Height != 1F;

            if (Current != null) throw new SystemException("Instance is already initialized.");
            Current = new Screen(window);
            return Current;

        }


        /// <summary>
        /// sets the focus to the specified control.
        /// </summary>
        /// <param name="control">The control to set the focus to.</param>
        /// <returns>True if the control has got the focus, otherwhise false (which means that it is either not in the tree or not enabled or visible).</returns>
        public bool Focus(Control control)
        {
            return messageServer.Focus(control);
        }

        public static void RenderCurrent()
        {
            if (Current != null) Current.Update();
        }


        internal bool HasFocus(Control control)
        {
            return messageServer.HasFocus(control);
        }

        void OnLoaded()
        {
            if (Loaded != null) Loaded(this, EventArgs.Empty);
        }

        /// <summary>
        /// Forces the event timer to progress. this is used in synchronous animations or transitions to ensure that
        ///  asynchrounous animations are also progressed while the synchronous animation is being executed in a loop.
        /// </summary>
        internal void ProgressTimer()
        {
            eventTimer.Progress();
        }


        /// <summary>
        /// Add a control to the end of the list if not already added.
        /// </summary>
        /// <param name="menu"></param>
        public void Attach(Control control)
        {
            Window w = control as Window;
            if (w != null) ActiveWindow = w;
            if (!screenControls.Contains(control))
            {
                AddControl(control);
            }
        }

        private void AddControl(Control control)
        {
            if (hardwareKeys == null || !(control is Window))
            {
                screenControls.Add(control);
            }
            else
            {
                screenControls.Prepend(hardwareKeys, control);
            }
            Layouter.Layout(control, Size);
        }


        /// <summary>
        /// Removes a control.
        /// </summary>
        /// <param name="menu"></param>
        /// <returns></returns>
        public void Detach(Control control)
        {
            screenControls.Remove(control);
            SetCurrentWindow();
        }

        private void SetCurrentWindow()
        {
            Window w = null;
            if (screenControls.Last is Window) w = screenControls.Last as Window;
            else
            {
                Control c = screenControls.First;
                while (c != null)
                {
                    Window w2 = c as Window;
                    if (w2 != null) w = w2;
                    c = c.NextControl;
                }
            }
            ActiveWindow = w;
        }

        /// <summary>
        /// Closes the application.
        /// </summary>
        public void Close()
        {
            ActiveWindow = null;
            screenControls.ClearAndDispose();
            this.Dispose();
            Screen.Current = null;
            (Host as System.Windows.Forms.Form).Close();
        }

        internal void SendToBack(FormBase form)
        {
            screenControls.Remove(form);
            screenControls.AddFirst(form);
        }

        internal void BringToFront(FormBase form)
        {
            screenControls.Remove(form);
            AddControl(form);
        }

        /// <summary>
        /// Captures the specified control and returns a texture that contains the complete image of the control.
        /// </summary>
        /// <param name="c">The control to capture.</param>
        /// <returns>A texture that contains the image of the specified control, otherwise null.</returns>
        public Texture Capture(Control c)
        {
            return Renderer.Capture(c);
        }


        /// <summary>
        /// Captures the complete screen.
        /// </summary>
        /// <returns>Texture that contains the image of the screen.</returns>
        public Texture Capture()
        {
            return Renderer.Capture(screenControls);
        }

        private bool GetAncestors(Control find, Control control, IList<Control> ancestors)
        {
            if (find == control) return true;
            ControlCollection controls = control.GetVisibleControls();
            if (controls != null)
            {
                Control c = controls.First;
                while (c != null)
                {
                    ancestors.Add(c);
                    if (GetAncestors(find, c, ancestors)) return true;
                    ancestors.RemoveAt(ancestors.Count - 1);
                    c = c.NextControl;
                }
            }
            return false;
        }


        /// <summary>
        /// Gets all ancestors of the specified control beginning from the root to grandparent, parent.
        /// </summary>
        /// <param name="control">The control for which to get all ancestors.</param>
        /// <returns>An enumeration of <see cref="T:Control"/>.</returns>
        public IEnumerable<Control> GetAncestors(Control control)
        {
            List<Control> controls = new List<Control>();
            Control c = screenControls.First;
            while (c != null)
            {
                controls.Add(c);
                if (GetAncestors(control, c, controls)) return controls;
                controls.RemoveAt(controls.Count - 1);
                c = c.NextControl;
            }
            return controls.ToArray();
        }

        /// <summary>
        /// Gets the actual TextColor for the specifed control.
        /// </summary>
        /// <param name="control">The control for which to get the TextColor</param>
        /// <returns>The TextColor for the control.</returns>
        /// <remarks>TextColor property is special since it is an entailed property.</remarks>
        public Color GetTextColor(Control control)
        {
            Color textColor = Renderer.StartTextColor;
            IEnumerable<Control> ancestors = GetAncestors(control);
            foreach (Control c in ancestors)
            {
                Color color = c.TextColor;
                if (!color.IsEmpty) textColor = color;
            }

            return textColor;
        }

        public void HideInputPanel()
        {
            inputPanel.Enabled = false;
        }

        private void OnOrientationChanged()
        {
            if (OrientationChanged != null) OrientationChanged(this, EventArgs.Empty);
        }


        public void RaiseViewChanged(FormBase form)
        {
            if (ViewChanged != null) ViewChanged(form, EventArgs.Empty);
        }

        /// <summary>
        ///  Invokes the specified delegate in the renderers thread. 
        /// </summary>
        /// <param name="d">Method to invoke.</param>
        public void InvokeThreaded(ThreadStart d)
        {
            var eventTimer = this.eventTimer;
            if (eventTimer != null)
            {
                lock (eventTimer)
                {
                    eventTimer.Invokes += d;
                    eventTimer.Enabled = true;
                }
            }
        }

        /// <summary>
        ///  Invokes the specified delegate in the renderers thread. 
        /// </summary>
        /// <param name="d">Method to invoke.</param>
        public void InvokeUnthreaded(ThreadStart d)
        {
            var eventTimer = this.eventTimer;
            if (eventTimer != null)
            {
                if (Thread.CurrentThread.ManagedThreadId != threadId)
                {
                    lock (eventTimer)
                    {
                        eventTimer.Invokes += d;
                        eventTimer.Enabled = true;
                    }
                }
                else
                {
                    eventTimer.Invokes += d;
                    eventTimer.Enabled = true;
                }
            }
        }

        internal void RaiseUnhandledException(Exception e)
        {
            if (Debugger.IsAttached) Debugger.Break();
            if (UnhandledException != null)
            {
                UnhandledExceptionEventArgs ea = new UnhandledExceptionEventArgs(e, false);
                UnhandledException(this, ea);
            }
            else
            {
                Silvermoon.Controls.Forms.MessageDialog.Show("Unhandled Exception", e.Message, "Continue", "Cancel", null, Close);
            }
        }

        internal void RaiseCloseEvent()
        {
            Window w = this.ActiveWindow;
            if (w != null) InvokeUnthreaded(w.OnExitApplication);
        }


        #endregion
        #region events

        public event UnhandledExceptionEventHandler UnhandledException;

        public event EventHandler OrientationChanged;

        /// <summary>
        /// Occurs once when rendering is available and rendering appeared the very first time.
        /// </summary>
        public event EventHandler Loaded;

        public event EventHandler<TimeEventArgs> Tick
        {
            add
            {
                if (eventTimer != null)
                {
                    eventTimer.Enabled = true;
                    eventTimer.Tick += value;
                }
            }
            remove
            {
                if (eventTimer != null) eventTimer.Tick -= value;
            }
        }

        public int NumberOfTickInvocations
        {
            get
            {
                return eventTimer != null ? eventTimer.NumberOfTickInvocations : 0;
            }
        }


        /// <summary>
        /// Occurs when the input mouse changes.
        /// </summary>
        public event EventHandler FocusChanged
        {
            add { messageServer.FocusChanged += value; }
            remove { messageServer.FocusChanged -= value; }
        }


        /// <summary>
        /// Occurs if any control causes the view to be changed. 
        /// This is the case, when e.g. a scrollable control scrolls into another position.
        /// </summary>
        public event EventHandler ViewChanged;

        #endregion

    }
}
